/*
 * Context.java
 *
 * Created on November 17, 2007, 4:38 PM
 *
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package hextd.core;

import hextd.HexTD;
import hextd.blackBox.BlackBox;
import hextd.core.gameloop.GameLoopManager;
import hextd.creep.Creep;
import hextd.message.MessageTick;
import hextd.tower.JsonTowerOptions;
import hextd.tower.Tower;
import hextd.tower.TowerFactory;
import hextd.tower.TowerFactory.type;
import hextd.util.TowerListener;
import hextd.util.WaveStartListener;
import hextd.wave.Path;
import hextd.wave.Wave;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;

/**
 *
 * @author hylke
 */
public abstract class Context {

	public MapInfo loadedMapInfo;
	public GameMap loadedGameMap = new GameMap();
	protected BlackBox blackBox;
	protected Map<type, JsonTowerOptions> towerOptions = new HashMap<TowerFactory.type, JsonTowerOptions>();

	public class GameTime {

		private int time = 0;

		public int getTime() {
			return time;
		}

		public synchronized int increment() {
			return ++time;
		}

		public synchronized void reset() {
			time = 0;
		}

		@Override
		public String toString() {
			return "" + time;
		}
	}
	protected final GameTime gameTime = new GameTime();
	protected HexTD.GameState gameState = HexTD.GameState.titleScreen;
	protected HexTD.GameResult gameResult = HexTD.GameResult.inactive;
	//
	public static final int actionDelay = 30;
	//
	private int localPlayer = 0;
	private ArrayList<ContextPlayer> players;
	private int totalPlayers = 1;
	private int activePlayers = 1;
	//
	private boolean waveReady = false;
	private boolean startWave = false;

	public Context() {
		players = new ArrayList<ContextPlayer>();
		// Add the first player
		players.add(new ContextPlayer(this));
	}

	public GameTime getGameTime() {
		return gameTime;
	}

	public void doTick(int time) {
		for (Creep e : getEnemies()) {
			e.doTick(time);
		}
		Tower[] towers = new Tower[getTowers().size()];
		towers = getTowers().toArray(towers);
		for (Tower t : towers) {
			t.doTick(time);
		}
		if (startWave) {
			startWave = false;
			tryNextWave();
		}
		ContextPlayer player = getLocalPlayer();
		if (player != null) {
			if (getGameState() == gameState.mpPlaying) {
				messageSend(MessageTick.formatMessage(getLocalPlayerId(), time));
				player.setLastTick(time);
			}
			if (getGameState() == gameState.mpCatchup) {
				player.setLastTick(time);
			}
		}
	}

	public boolean tryNextWave() {
		if (getWaveCount() <= 0 || getWave() > getWaveCount()) {
			return false;
		}
		if (waveReady && getWave() < getWaveCount()) {
			startWave = false;
			waveReady = false;

			getBlackBox().nextWave(-1);
		}
		return true;
	}

	abstract public HexTD.GameResult getGameResult();

	abstract public void setGameResult(HexTD.GameResult gameResult);

	abstract public void endGame();

	abstract public MapInfo getMapInfo();

	abstract public boolean loadMap(String map, String expectedMD5);

	abstract public boolean loadMap(MapInfo map, String expectedMD5);

	abstract public void resetMap();

	abstract public HexTD.GameState getGameState();

	abstract public void setGameState(HexTD.GameState gameState);

	abstract public URL getContextUrl();

	abstract public void setContextUrl(URL contextUrl);

	abstract public URL getMapContextUrl();

	abstract public void setMapContextUrl(URL mapContextUrl);

	abstract public String getMapLocation();

	abstract public String getLevelTitle();

	abstract public void setLevelTitle(String levelTitle);

	abstract public String getLevelInfo();

	abstract public void setLevelInfo(String levelInfo);

	abstract public String getBackgroundImage(boolean canDefault);

	abstract public void setBackgroundImage(String backgroundImage);

	public void resetTowerOptions() {
		towerOptions.clear();
	}

	public void setTowerOptions(TowerFactory.type type, JsonTowerOptions options) {
		towerOptions.put(type, options);
	}

	public boolean newTowerAllowed(TowerFactory.type type, int player) {
		JsonTowerOptions options = towerOptions.get(type);
		if (options == null) {
			return true;
		}
		return options.getMaxCount() > getPlayer(player).getTowerCount(type);
	}

	/**
	 * Create and return a new GameGrid and make it the active GameGrid in this
	 * context.
	 * @return the newly created GameGrid
	 */
	abstract public GameGrid newGameGrid();

	/**
	 * Get the GameGrid that is currently active.
	 * @see Context.newGameGrid
	 * @return the current GameGrid
	 */
	abstract public GameGrid getGameGrid();

	abstract public int getLivesStart();

	abstract public void setLivesStart(int livesStart);

	abstract public long getCreditsStart();

	abstract public void setCreditsStart(long creditsStart);

	abstract public int getInterestStart();

	abstract public void setInterestStart(int interestStart);

	abstract public int getInterestIncrease();

	abstract public void setInterestIncrease(int interestIncrease);

	abstract public int getEnemyCount();

	abstract public ArrayList<Creep> getEnemies();

	public Collection<Creep> getTargets(int player) {
		if (player >= 0 && player < players.size()) {
			ContextPlayer cp = players.get(player);
			if (cp != null) {
				return cp.getTargets();
			}

		}
		return getEnemies();
	}

	abstract public int getUniqueSpriteId();

	abstract public void addEnemies(Collection<Creep> newEnemies);

	abstract public void enemyDied();

	abstract public void removeAllEnemies();

	abstract public ArrayList<Tower> getTowers();

	abstract public void addTower(Tower t);

	abstract public void removeTower(Tower t);

	abstract public void newTower(int col, int row, TowerFactory.type t);

	abstract public void sellTower(Tower t);

	public void upgradeTower(Tower t) {
		int[] hexC = t.getHex().getLocation();
		blackBox.upgradeTower(-1, hexC[0], hexC[1], -1, t.getOwner(), true);
	}

	abstract public void clearTowers();

	abstract public void addTowerListener(TowerListener l);

	abstract public void removeTowerListener(TowerListener l);

	abstract public void nextMode(Tower t, int m, int step);

	abstract public void setMode(Tower t, int m, int v);

	public void buyCreeps(Tower t, int type, int count, int size, int path, int health) {
		int[] hexC = t.getHex().getLocation();
		this.blackBox.buyCreeps(-1, hexC[0], hexC[1], type, count, size, path, health, -1, t.getOwner(), true);
	}

	public int getActivePlayers() {
		return activePlayers;
	}

	public void setActivePlayers(int nr) {
		activePlayers = nr;
	}

	protected List<ContextPlayer> getPlayers() {
		return players;
	}

	public int getTotalPlayers() {
		return totalPlayers;
	}

	public void clearPlayers() {
		players.clear();
		ContextPlayer p = new ContextPlayer(this);
		p.setActive(true);
		players.add(p);
		totalPlayers = 1;
		activePlayers = totalPlayers;
	}

	public void ensurePlayer(int p) {
		while (p + 1 > this.players.size()) {
			this.players.add(new ContextPlayer(this));
		}
		switch (gameResult) {
			case loading:
				for (ContextPlayer player : this.players) {
					player.setActive(true);
				}
				this.totalPlayers = Math.max(this.totalPlayers, p + 1);
				this.activePlayers = this.totalPlayers;
				break;
		}
	}

	public ContextPlayer getPlayer(int playerId) {
		if (playerId < players.size()) {
			return players.get(playerId);
		} else {
			return null;
		}
	}

	public ContextPlayer getLocalPlayer() {
		return getPlayer(localPlayer);
	}

	public int getLocalPlayerId() {
		return localPlayer;
	}

	public void setLocalPlayerId(int localPlayer) {
		this.localPlayer = localPlayer;
	}

	abstract public void playerDied(ContextPlayer player);

	/**
	 * Sets the number of lives for all players to n.
	 * @param n
	 */
	public void resetLives(int n) {
		for (ContextPlayer cp : players) {
			cp.setLives(n);
			fireLivesChangedEvent(cp);
		}
	}

	/**
	 * Resets the score of all players.
	 */
	public void resetScore() {
		for (ContextPlayer cp : players) {
			cp.setScore(0l);
		}
	}

	/**
	 * Sets the credits of ALL players
	 * @param credits
	 */
	public void resetCredits(long credits, int bonus) {
		for (ContextPlayer cp : players) {
			cp.resetCredits(credits, bonus);
			fireMoneyChangedEvent(cp);
		}
	}

	protected void payInterest() {
		for (ContextPlayer cp : this.players) {
			cp.payInterest();
			this.fireMoneyChangedEvent(cp);
		}
	}

	public void resetTicks() {
		for (ContextPlayer cp : this.players) {
			cp.setLastTick(0);
		}
	}

	public int getLowestTick() {
		int lowest = Integer.MAX_VALUE;
		for (ContextPlayer cp : this.players) {
			if (cp.isActive()) {
				lowest = Math.min(lowest, cp.getLastTick());
			}
		}
		return lowest;
	}

	public ContextPlayer getSlowestPlayer() {
		int lowest = Integer.MAX_VALUE;
		ContextPlayer p = null;
		for (ContextPlayer cp : this.players) {
			if (cp.isActive()) {
				if (cp.getLastTick() < lowest) {
					p = cp;
					lowest = cp.getLastTick();
				}
			}
		}
		return p;
	}

	abstract public void fireLivesChangedEvent(ContextPlayer player);

	abstract public void fireMoneyChangedEvent(ContextPlayer player);

	abstract public void messageSend(String line);

	abstract public ArrayList<Wave> getWaves();

	abstract public int getWave();

	abstract public int getWaveCount();

	abstract public void nextWave();

	abstract public boolean isWaveActive();

	abstract public void setWaveActive(boolean waveActive);

	abstract public void addWaveStartListener(WaveStartListener l);

	abstract public void removeWaveStartListener(WaveStartListener l);

	abstract public ArrayList<Path> getPaths();

	abstract public Path getPath(int path);

	abstract public Collection<Path> getPaths(int player);

	abstract public void showAlert(String alert);

	abstract public void setDebug(String string);

	abstract public void setStatus(String string);

	abstract public BlackBox getBlackBox();

	abstract public void setBlackBox(BlackBox b);

	abstract public void hexChanged();

	abstract public void setGameLoop(GameLoopManager gameLoop);

	/**
	 * @return wether a wave is ready to start
	 */
	public boolean isWaveReady() {
		return waveReady;
	}

	/**
	 * @param set whether a wave is ready to start
	 */
	public void setWaveReady(boolean waveReady) {
		this.waveReady = waveReady;
	}

	/**
	 * @return wether a wave has been triggered
	 */
	public boolean isStartWave() {
		return startWave;
	}

	/**
	 * @param trigger the next wave as soon as possible
	 */
	public void setStartWave(boolean startWave) {
		this.startWave = startWave;
	}
}
